package hyl.base.safe;

import hyl.core.MyCharset;
import hyl.core.MyFun;
import hyl.core.fun.MyByte;
import hyl.core.safe.MyBase64;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * sm4 需要用在key不断变化的场合, 所以不适合做成类 ,做成对象(封装 key 和 密码本)没有意义
 * 
 * 
 * @description : SM4加密算法工具类--有些涉及线性代数的数学方面的可能理解有点跑偏.凑活看
 *              属于对称加密算法,可用于替代DES/AES等算法,采用分组加密
 *              ,且SM4算法同AES算法具有相同的密钥长度和分组长度,都是128bits[16进制==>32位].
 *              整个加解密部分可以拆分成密钥扩展和加解密两步 密钥扩展: 一堆线性函数我是没看太懂.简单理解下感觉就是
 *              1.对密钥进行分组后得到MK=(MK0,MK1,MK2,MK3)
 *              2.根据(MK0,MK1,MK2,MK3)得到(K0,K1,K2,K3)=(MK0 ⊕ FK0,MK1 ⊕ FK1 , MK2
 *              ⊕ FK2 , MK3 ⊕ FK3) 其中:FK为系统参数.16进制表示类似于:
 *              FK0=(a3b1c6)知道这个就好了.具体的数学学得太垃圾.搞不懂 3.因为加密时要进行非线性迭代32次 .
 *              所以这里生成32位轮密钥:rk0~rk31 rk[i] = k[i+4] = K[i] ⊕ T'(K[i+1] ⊕ K[i+2]
 *              ⊕ k[i+3] ⊕ CKi) 其中: 1.T变换和加密算法中的轮函数基本相同 , 只是将其中的线性变换L换成了:L[B] =
 *              B ⊕ (B <<< 13) ⊕ (B <<< 23) 2.
 *              固定参数CK的取值方法为:设ck[i,j]为CK[i]的第j字节(i=0~31 , j=0~3), 即CK[i] =
 *              (ck[i,0],ck[i,1],ck[i,2],ck[i,3])∈(Z[下标2,上标8]4次方)-依稀记得以前见过这个写法.忘记代表啥了
 *              其中:ck[i,j] = (4i + j) * 7 (mod 256)共得到32个CKi
 *              加密过程:包括32次轮迭代[就是非线性迭代]和反序迭代[异或]两步 32次轮迭代: 分组明文拆分为(A0
 *              ,A1,A2,A3)结合密码扩展得到的32组轮密码.轮函数F进行32次迭代
 *              Xi+4=F(Xi,Xi+1,Xi+2,Xi+3,rki)=Xi⊕T(Xi+1⊕Xi+2⊕Xi+3⊕rki),i=0,1,⋯,31
 *              合成置换 T:Z322→Z322 是一个可逆变换，由非线性变换 τ 和线性变换 L复合而成，即 T(⋅)=L(τ(⋅))。
 *              线性变换 L： L(B)=B⊕(B<<<2)⊕(B<<<10)⊕(B<<<18)⊕(B<<<24)。 非线性变换 τ：
 *              τ(B)，τ 由4个并行的S盒构成。 反序迭代:得到的x4~x35中得到x32~x35 总结下来就是 1. 先将128比特密钥
 *              MK 扩展为32个轮密钥 rk， 2. 再将该轮密钥与128比特明文 X 经过轮函数进行32次迭代后，选取最后四次迭代生成的结果
 *              X32,X33,X34,X35 进行反序变换， 该变换结果作为最终的密文 Y 输出 解密时算法内容大致一样 .
 *              只不过轮密钥顺序相反
 * 
 */
public class MySm4 {
	// CBC模式：不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。需要初始化向量IV
	// ECB模式（电子密码本模式：Electronic codebook）ECB模式

	// 1、简单；2、有利于并行计算；3、误差不会被传送；
	public static final String ALGORITHM_NAME = "SM4";
	// 加密算法/分组加密模式/分组填充方式
	// PKCS5Padding-以8个字节为一组进行分组加密
	// 定义分组加密模式使用：PKCS5Padding
	public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
	// 128-32位16进制；256-64位16进制
	public static final int DEFAULT_KEY_SIZE = 128;
	static final String APPEND_SEPARATOR = "/";
	private static volatile boolean initResult;

	private static Object lock = new Object();

	public static void f注册() {
		if (!initResult) {
			synchronized (lock) {
				if (!initResult) {
					try {

						// 为防止当前加密支持器影响该算法,先将当前加密支持器删除
						Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
						// 添加SM加密算法相关支持器

						Security.addProvider(new BouncyCastleProvider());
						initResult = true;
					} catch (Exception e) {
						// LOGGER.error("init failed:{}", e.getMessage(), e);
					}
				}
			}
		}
	}

	/**
	 * 自动生成密钥
	 * 
	 * @explain
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 */
	public static byte[] g生成密钥() throws Exception {
		return g生成密钥(DEFAULT_KEY_SIZE);
	}

	/**
	 * @explain
	 * @param keySize
	 * @return
	 * @throws Exception
	 */
	public static byte[] g生成密钥(int keySize) throws Exception {
		KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
		kg.init(keySize, new SecureRandom());
		return kg.generateKey().getEncoded();
	}

	private static Cipher g生成密码算法(String 算法名, E加密模式 分组加密模式, E加密填充模式 分组填充算法, int cipherMode, byte[] key, byte[] iv)
			throws Exception {
		String algorithmNameModePadding = 算法名 + APPEND_SEPARATOR + 分组加密模式.get模式名() + APPEND_SEPARATOR
				+ 分组填充算法.getCode();

		// Cipher:为加密和解密提供密码功能. 是JCE的核心.
		// 初始化该类需提供需要转换的算法名
		Cipher cipher = Cipher.getInstance(algorithmNameModePadding, BouncyCastleProvider.PROVIDER_NAME);
		Key sm4Key = new SecretKeySpec(key, 算法名);
		boolean needIV = 分组加密模式.is需要向量();

		if (needIV) {

			// 指定一个初始化向量 . 防止后续加密的过程中被篡改 . 主要用于反馈模式的加密.如 CBC,CFB 等
			IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
			cipher.init(cipherMode, sm4Key, ivParameterSpec);

		} else {

			cipher.init(cipherMode, sm4Key);

		}
		return cipher;
	}

	/**
	 * 生成初始化向量--CBC/CFB等模式下使用
	 * 
	 * @param keySize
	 * @return
	 * @throws Exception
	 */
	public static byte[] g生成密码算法向量(String 算法名, int 向量长度) throws Exception {
		KeyGenerator kg = KeyGenerator.getInstance(算法名, BouncyCastleProvider.PROVIDER_NAME);
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
		kg.init(向量长度, random);
		return kg.generateKey().getEncoded();
	}

	/**
	 * 加密
	 * 
	 * @param 算法名
	 * @param 分组加密模式
	 * @param 分组填充算法
	 * @param param  待加密参数
	 * @return
	 */
	static byte[] encrypt(E加密模式 分组加密模式, E加密填充模式 分组填充算法, byte[] 明文, byte[] 密钥) {
		if (MyFun.isEmpty(密钥))
			return null;

		try {

			boolean needIV = 分组加密模式.is需要向量();
			Cipher cipher = null;
			if (needIV) {
				byte[] iv = g生成密码算法向量(ALGORITHM_NAME, DEFAULT_KEY_SIZE);
				cipher = g生成密码算法(ALGORITHM_NAME, 分组加密模式, 分组填充算法, Cipher.ENCRYPT_MODE, 密钥, iv);
				byte[] cipherArray = cipher.doFinal(明文);
				return MyFun.concat(iv, cipherArray);

			} else {
				cipher = g生成密码算法(ALGORITHM_NAME, 分组加密模式, 分组填充算法, Cipher.ENCRYPT_MODE, 密钥, null);
				return cipher.doFinal(明文);
			}
		} catch (Exception e) {
			e.printStackTrace();
//            throw new CloudTravelException(String.format("加密异常, algorithmName={%s}, algorithmMode={%s} ," +
//                            "algorithmPadding={%s} ,param={%s},message={%s}" , algorithmName.getName(),
//                    algorithmMode.getModeName() , algorithmPadding.name() , param , e.getMessage()));
		}
		return null;
	}

	/**
	 * 解密
	 * 
	 * @param 算法名
	 * @param 分组加密模式
	 * @param 填充方式---解密时需逆向截取加密的明文
	 * @param param                待解密参数
	 * @return
	 */
	static byte[] decrypt(E加密模式 分组加密模式, E加密填充模式 填充方式, byte[] 密钥, byte[] 密文) {
		if (MyFun.isEmpty(密文))
			return null;
		try {
			byte[] cipherArray = null;
			boolean containsIV = 分组加密模式.is需要向量();
			Cipher cipher = null;
			if (containsIV) {
				int 边界 = DEFAULT_KEY_SIZE / 8;
				// DEFAULT_KEY_SIZE
				byte[] 向量 = MyByte.subBytes(密文, 0, 边界);
				密文 = MyByte.subBytes(密文, 边界);
//				String paramStr = param.substring(0, param.indexOf("_"));
//				String ivStr = param.substring(param.indexOf("_") + 1);
				// byte[] ivBytes = ByteUtils.fromHexString(ivStr);
				cipher = g生成密码算法(ALGORITHM_NAME, 分组加密模式, 填充方式, Cipher.DECRYPT_MODE, 密钥, 向量);
			} else {
				cipher = g生成密码算法(ALGORITHM_NAME, 分组加密模式, 填充方式, Cipher.DECRYPT_MODE, 密钥, null);
			}
			cipherArray = cipher.doFinal(密文);
			// decryptStr = new String(cipherArray, MyCharset._UTF8);
			return cipherArray;
		} catch (Exception e) {
			// throw new CloudTravelException(String.format("解密异常, algorithmName={%s},
			// algorithmMode={%s} ," +
			// "algorithmPadding={%s} ,param={%s},message={%s}" , algorithmName.getName(),
			// algorithmMode.getModeName() , algorithmPadding.name() , param ,
			// e.getMessage()));
		}
		return null;

	}

	public static String do加密16(E加密模式 分组加密模式, E加密填充模式 分组填充算法, String 十六进制密钥, String 明文) {
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return MyByte.bytesToHex(encrypt(分组加密模式, 分组填充算法, 明文.getBytes(MyCharset._UTF8), keyData));
	}

	public static String do解密16(E加密模式 分组加密模式, E加密填充模式 分组填充算法, String 十六进制密钥, String 十六进制密文) throws Exception {
		f注册();
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		// hexString-->byte[]
		byte[] cipherData = MyByte.hexToBytes(十六进制密文);
		// 解密
		byte[] srcData = decrypt(分组加密模式, 分组填充算法, keyData, cipherData);
		// byte[]-->String
		return new String(srcData, MyCharset._UTF8);
	}

	public static String do加密64(E加密模式 分组加密模式, E加密填充模式 分组填充算法, String 十六进制密钥, String 明文) {
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return MyBase64.encode(encrypt(分组加密模式, 分组填充算法, 明文.getBytes(MyCharset._UTF8), keyData));
	}

	public static String do解密64(E加密模式 分组加密模式, E加密填充模式 分组填充算法, String 十六进制密钥, String base64密文) {
		f注册();
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		// hexString-->byte[]
		byte[] cipherData = MyBase64.decode(base64密文);
		// 解密
		byte[] srcData = decrypt(分组加密模式, 分组填充算法, keyData, cipherData);
		// byte[]-->String
		return new String(srcData, MyCharset._UTF8);
	}

	/**
	 * sm4加密
	 * 
	 * @explain 加密模式：ECB 密文长度不固定，会随着被加密字符串长度的变化而变化
	 * @param hexKey   16进制密钥（忽略大小写）
	 * @param paramStr 待加密字符串
	 * @return 返回16进制的加密字符串
	 * @throws Exception
	 */
	public static String ecb加密16(String 十六进制密钥, String 明文) {
		// 16进制字符串-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return ecb加密16(keyData, 明文);
	}

	static byte[] ecb带盐加密(byte[] 密钥, String 明文, int 盐长度) {
		// 16进制字符串-->byte[]
		byte 盐[] = null;
		if (盐长度 > 0)
			盐 = MyByte.g生成随机串(盐长度);
		return ecb加密(密钥, 明文.getBytes(MyCharset._UTF8), 盐);
	}

	public static String ecb带盐加密16(String 十六进制密钥, String 明文, int 盐长度) {
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return MyByte.bytesToHex(ecb带盐加密(keyData, 明文, 盐长度));
	}

	public static String ecb带盐加密16(byte[] 密钥, String 明文, int 盐长度) {
		return MyByte.bytesToHex(ecb带盐加密(密钥, 明文, 盐长度));
	}

	public static String ecb加密16(byte[] 密钥, String 明文) {
		// 16进制字符串-->byte[]
		return MyByte.bytesToHex(ecb加密(密钥, 明文.getBytes(MyCharset._UTF8)));
	}

	public static String ecb加密64(String 十六进制密钥, String 明文) {
		// 16进制字符串-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return MyBase64.encode(ecb加密(keyData, 明文.getBytes(MyCharset._UTF8)));
	}

	public static byte[] ecb加密(byte[] 密钥, byte[] 明文) {
		return ecb加密(密钥, 明文, null);
	}

	public static byte[] ecb加密(byte[] 密钥, byte[] 明文, byte[] 盐) {
		f注册();
		SecretKey secretKey = new SecretKeySpec(密钥, ALGORITHM_NAME);
		try {
			Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
			cipher.init(Cipher.ENCRYPT_MODE, secretKey);
			if (盐 == null) {
				return cipher.doFinal(明文);
			} else {
				byte[] bb = MyFun.concat(盐, 明文);
				byte b = (byte) 盐.length;
				return MyFun.concat(new byte[] { b }, 盐, cipher.doFinal(bb));
			}
		} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException
				| IllegalBlockSizeException | BadPaddingException e) {
			e.printStackTrace();
			return null;
		}

	}

	public static byte[] ecb解密(byte[] 密钥, byte[] 密文, boolean 加盐) {
		f注册();
		// byte[] keyData = MyByte.hexToBytes(hexKey);
		try {
			SecretKey secretKey = new SecretKeySpec(密钥, ALGORITHM_NAME);
			Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
			cipher.init(Cipher.DECRYPT_MODE, secretKey);
			if (加盐) {
				int 盐长度 = 密文[0];
				byte[] mm = MyByte.subBytes(密文, 盐长度+1);
				byte[] m2 = cipher.doFinal(mm);
				return MyByte.subBytes(m2, 盐长度);
			} else
				return cipher.doFinal(密文);
		} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException
				| IllegalBlockSizeException | BadPaddingException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 16进制密钥（忽略大小写）
	 * 
	 * 16进制的加密字符串（忽略大小写）
	 * 
	 * @return 解密后的16进制字符串
	 * @throws Exception
	 */
	public static String ecb解密16(String 十六进制密钥, String 十六进制密文) {

		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return ecb解密16(keyData, 十六进制密文);
	}

	public static String ecb带盐解密16(String 十六进制密钥, String 十六进制密文) {
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		return ecb解密16(keyData, 十六进制密文, true);
	}

	public static String ecb解密16(byte[] 密钥, String 十六进制密文, boolean 加盐) {
		f注册();
		// hexString-->byte[]
		byte[] cipherData = MyByte.hexToBytes(十六进制密文);
		// 解密
		byte[] srcData;
		srcData = ecb解密(密钥, cipherData, 加盐);
		// byte[]-->String
		return new String(srcData, MyCharset._UTF8);

	}

	public static String ecb解密16(byte[] 密钥, String 十六进制密文) {
		return ecb解密16(密钥, 十六进制密文, false);
	}

	public static String ecb解密64(String 十六进制密钥, String base64密文) {
		return ecb解密64(十六进制密钥, base64密文, false);
	}

	public static String ecb解密64(String 十六进制密钥, String base64密文, boolean 带盐) {
		f注册();
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		// hexString-->byte[]
		byte[] cipherData = MyBase64.decode(base64密文);
		// 解密
		byte[] srcData = ecb解密(keyData, cipherData, 带盐);
		// byte[]-->String
		return new String(srcData, MyCharset._UTF8);
	}

	/**
	 * 校验加密前后的字符串是否为同一数据
	 * 
	 * @explain
	 * @param 十六进制密钥     （忽略大小写）
	 * @param cipherText 16进制加密后的字符串
	 * @param paramStr   加密前的字符串
	 * @return 是否为同一数据
	 * @throws Exception
	 */
	public static boolean ecb验证16(String 十六进制密钥, String 明文, String 十六进制密文) {
		// 用于接收校验结果
		boolean flag = false;
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		// MyByte.hexToBytes(paramStr);
		// 将16进制字符串转换成数组
		byte[] cipherData = MyByte.hexToBytes(十六进制密文);
		// 解密
		byte[] decryptData = ecb解密(keyData, cipherData, false);
		// 将原字符串转换成byte[]
		byte[] srcData = 明文.getBytes(MyCharset._UTF8);
		// 明文前后对比
		flag = Arrays.equals(decryptData, srcData);
		return flag;
	}

	public static boolean ecb验证64(String 十六进制密钥, String 明文, String b64密文) {
		// 用于接收校验结果
		boolean flag = false;
		// hexString-->byte[]
		byte[] keyData = MyByte.hexToBytes(十六进制密钥);
		// MyByte.hexToBytes(paramStr);
		// 将16进制字符串转换成数组
		byte[] cipherData = MyBase64.decode(b64密文);
		// 解密
		byte[] decryptData = ecb解密(keyData, cipherData, false);
		// 将原字符串转换成byte[]
		byte[] srcData = 明文.getBytes(MyCharset._UTF8);
		// 明文前后对比
		flag = Arrays.equals(decryptData, srcData);
		return flag;
	}

	public static void main(String[] args) {
		try {
			String 明文 = "13800138000";
			// 自定义的32位16进制密钥
			String key = "2B90F5D5F066C4BA52D53646CEE64C5A".toLowerCase();

			// key = "0EaEEa8A88a9B";
			// byte[] keyData1 = MyByte.hexToBytes(key);
			// byte[] keyData2 = MyByte.hexToBytes(key);
			// MyFun.print(Arrays.equals( keyData1,keyData2));
			String 密文 = MySm4.ecb加密16(key, 明文);
			MyFun.print("ecb加密16", 密文);
			MyFun.print("ecb验证16", MySm4.ecb验证16(key, 明文, 密文));// true
			String 明文2 = MySm4.ecb解密16(key, 密文);
			MyFun.print("ecb解密16", 明文2);

			密文 = MySm4.ecb加密64(key, 明文);
			MyFun.print("ecb加密64", 密文);
			MyFun.print("ecb验证64", MySm4.ecb验证64(key, 明文, 密文));// true
			明文2 = MySm4.ecb解密64(key, 密文);
			MyFun.print("ecb解密64", 明文2);

			key = "86C63180C2806ED1F47B859DE501215B";

			密文 = MySm4.do加密16(E加密模式.CBC, E加密填充模式.PKCS5_PADDING, key, 明文);
			MyFun.print("do加密16", 密文);
			明文2 = MySm4.do解密16(E加密模式.CBC, E加密填充模式.PKCS5_PADDING, key, 密文);
			MyFun.print("do解密16", 明文2);

			密文 = MySm4.do加密64(E加密模式.CBC, E加密填充模式.PKCS5_PADDING, key, 明文);
			MyFun.print("do加密16", 密文);
			明文2 = MySm4.do解密64(E加密模式.CBC, E加密填充模式.PKCS5_PADDING, key, 密文);
			MyFun.print("do解密16", 明文2);

			// ecb加密16 与js 互通
			明文 = "hel1o world!我是juneandgreen.";
			key = "abcdefabcdef98765432109876543210";
			密文 = MySm4.ecb加密16(key, 明文);
			System.out.println(密文);
			明文2 = MySm4.ecb解密16(key, 密文);
			System.out.println(明文2);
			// c265245e7658c9d2d57d226435b0924cfbd8cc11e48378840bb80364a91b9d3f
			// c265245e7658c9d2d57d226435b0924cfbd8cc11e48378840bb80364a91b9d3f

			密文 = MySm4.ecb带盐加密16(key, 明文, 6);
			MyFun.print("ecb带盐加密16", 密文);
			明文2 = MySm4.ecb带盐解密16(key, 密文);
			MyFun.print("ecb带盐解密16", 明文2);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
